#include "Pvcalc.h"

#include <sstream>
#include <map>
#include <string>
#include <math.h>

#include "BigInt.h"


using namespace std;


Pvcalc::Pvcalc( int width , int height , float score ,unsigned  int accurracy , bool print)
: _width(width)
, _height(height)
, _score(score)
, _accurracy(accurracy)
, _print(print)
{

}

/*****************************************************************************/

Pvcalc::~Pvcalc()
{

}



BigInt factorial_2(BigInt startValue , BigInt loop_s , BigInt loop_e)
{
  //cout << "facto2" << endl ;
  BigInt valueToReturn = startValue ;
  for (BigInt i =  loop_s+1; i <= loop_e; i = i + 1 )
  {
    valueToReturn  = valueToReturn * i;
    //cout <<"i="<<i<<" value=" << valueToReturn << endl ;
  }
  return valueToReturn;
}

// code for factorial function
BigInt factorial(BigInt n)
{
  //cout << "facto1" << endl ;
  BigInt valueToReturn = 1 ;
  for (BigInt i =  2; i <= n; i = i + 1 )
  {
    valueToReturn  = valueToReturn * i;
    //cout <<"i="<<i<<" value=" << valueToReturn << endl ;
  }
  return valueToReturn;
}
// code for combination function 
BigInt combination(BigInt n, BigInt m)
{

  if(n>m)
  {       
    BigInt factm = factorial(m)  ;   
    BigInt fact_nm = factorial_2(factm , m , (n-m) )  ;
    BigInt factn = factorial_2(fact_nm , (n-m) , n )  ;

    BigInt result =  factn / ( factm * fact_nm);   

    return result ;    
  }  

  // just in case....
  BigInt result = factorial(n) / (factorial(m) * factorial(n-m));
  return result ;
}


string Pvcalc::compute()
{

  int   Nhit	    = this->_width	;
  int   Nmiss	    = this->_height	; 
  float constante = 1.f / Nmiss  * 100.0;
  float score     = this->_score  ;

  if(_print)cout << "\tpath finding ..."<< endl ;

  float Xoffset = score * ( Nhit  + 1.0 ) ;
  float Yoffset = score * ( Nmiss + 1.0 ) ;

  //cout << "Xoffest ="<< Xoffset << " Yoff=" << Yoffset << endl ;

  /*1st Equation , Minimum 'y'*/
  pair <float,float> pt1 ;
  pair <float,float> pt2 ;

  pt1.first  = Xoffset ;
  pt1.second = 0.0 ;

  pt2.first  = (float)Nhit ;
  pt2.second = (float)Nmiss - (float)Yoffset ;

  float a_min , b_min , c_min ;
  this->findEquation( pt1 , pt2 , a_min , b_min , c_min );

  //cout << "\n eq mini :" << a_min << " " <<  b_min << " " << c_min << endl ;


  /*2d Equation , Maximum 'y'	*/
  pt1.first  = 0 ;
  pt1.second = Yoffset ;

  pt2.first  = (float)Nhit - (float)Xoffset ;
  pt2.second = (float)Nmiss ;

  float a_max , b_max , c_max ;
  this->findEquation( pt1 , pt2 , a_max , b_max , c_max );

  // cout << "\n eq maxi :" << a_max << " " <<  b_max << " " << c_max << endl ;


  map< string, BigInt> prev; 
  map< string, BigInt> current; 

  stringstream ss;
  string str;
  float  minY ;
  float  maxY ;

  //doing the first line
  for( float x = 0 ;  x <= Nhit  ;  x++ )
  {
    if( b_min != 0 ){ minY = ( -  c_min -  a_min *  x) /  b_min ;}else{ minY= -1.0;}

    if( b_max != 0 ){ maxY = ( -  c_max -  a_max *  x) /  b_max ;}else{ maxY= Nmiss + 1.0;}

    if(  minY < 0.0  &&  0.0 <  maxY )
    {
      //cout << "x=" << x << " is ok " << endl ;
      ss.clear();
      str.clear();
      ss << x <<"," << 0  ; 
      ss >> str; 

      prev[ str ] = (BigInt) 1 ; 
    }
  }


  //double test=  (*prev)[ "0,0" ]  ;cout << "test=" << test << endl ; 
  //system("pause");

  const int cursor = 5 ;

  BigInt last(0);
  stringstream ss2   ;
  string       str2  ;
  // fix the Top neighbour
  BigInt TneighbourValue(0);
  // fix the Right neighbour
  BigInt RneighbourValue(0);
  float  maxX ;
  float  minX ;

  // doing other lines
  for( float  y = 1 ;  y <=   Nmiss  ;  y++ )
  {	

    if( a_min != 0 ){ maxX = ( -  c_min -  b_min *  y) /  a_min ;}else{ maxX= Nhit ;}
    maxX = ceil(maxX);
    if(maxX > Nhit ){ maxX = Nhit  ;} else{maxX--;}

    if( a_max != 0 ){ minX = ( -  c_max -  b_max *  y) /  a_max ;}else{ minX= 0.0;}
    minX = floor(minX);
    if(minX < 0.0){ minX = 0.0 ; }else{minX++;}

    for( float  x = minX ; x <=   maxX  ;  x++ )
    {		
      ss.clear();
      str.clear();
      
      ss << (x-1.0) <<"," << y  ; ss >> str; 
      RneighbourValue = current[str];

      //cout << "gauche de ("<<x<<","<<y<<") = "<< str <<" = "<< RneighbourValue <<endl;
      //cout << "gauche de ("<<x<<","<<y<<") = "<<"RneighbourValue="<<RneighbourValue <<endl ;


      ss2.clear();
      str2.clear();
      ss2 << x <<"," << (y-1.0)  ; 
      ss2 >> str2; 
      TneighbourValue = prev [str2];
      //cout << "bas de ("<<x<<","<<y<<") = "<< str2 <<" = "<< TneighbourValue <<endl;
      //cout << "bas de ("<<x<<","<<y<<") = "<<"TneighbourValue="<<TneighbourValue <<endl ;

      stringstream ss3   ;
      string       str3  ;

      last =  RneighbourValue +  TneighbourValue ;
      ss3 << x <<"," <<  y   ; 
      ss3 >> str3; 
      current[str3] =  last ;

      //cout << "last = " << last.toString()  << endl ;
      //cout <<"total="<< x <<"," << y << "=" << last << endl ;

      //float step = Nmiss / 10 ;
      //if ( y == step  ){cout << "\ny="<< y ; step+= ( Nmiss / 10) ; }

      //else{cout << "on ne fait pas la case " << x<<","<<y<< "car minY="<<minY << " maxY="<<maxY<<endl;}			
    }
    prev.clear();
    prev = current ;
    current.clear();

    // TODO : remplacer par une constante
    int a = (int) ( y * constante );
    //cout << "a" << a << "," << ( (y/Nmiss)  * 100.0 ) <<endl ; 
    if(_print) if( a  % cursor == 0 ){cout << "\r\t" <<a <<" %"  ;}

  }
  

  //printf("\nways     ="); cout << last << endl ;
  //cout <<"BI last   =" << last << endl ;

  if( last <=  0 )
  {
      return "0" ;
  } 
  

  if(_print)cout << "\n\t combinations ..."  ; 
  BigInt comb =    combination( Nhit+Nmiss , Nmiss );
  //printf("\ncomb     =");cout << comb   << endl ;
  //cout <<"BI comb   =" << comb << endl ;
  if(_print)cout << "ok" <<endl ; 


  if(_print)cout << "\t calcul ..."  ; 

  BigInt difference = comb - last ;
  //cout <<"BI diff   =" << difference << endl ;

  int a = comb.myNumDigits ;
  int b = difference.myNumDigits ;
  unsigned int numDiff = (a-b) ;
  //cout << "numb dff=" << numDiff << endl ;  

  if(this->_accurracy < numDiff ){if(_print)cout <<"\n";return "0" ;}
  else{ numDiff = this->_accurracy ; }

  BigInt toDiv = difference ;
  for( unsigned int i = 0 ; i< numDiff   ; i++)
  {
    toDiv = toDiv * 10 ;
  }
  //cout <<"toDiv     =" << toDiv << endl ;

  BigInt res = toDiv / comb ;
  res = res / 2 ;
  //cout <<"res     =" << res <<" x 10^-"<< numDiff << endl ;
  //BigInt difference = big - getdig ;
  if(_print)cout << "ok" <<endl ; 


  // formatage du resultat 
  string resultat = res.ToString();

  for ( unsigned int i = res.myNumDigits  ; i <  this->_accurracy  ; i++ )
  {
    resultat = "0"+resultat;
  }
  resultat = "0."+resultat;

  return resultat;
}





void Pvcalc::findEquation( pair<float,float> &pt1 , pair<float,float>& pt2 , float& a ,  float& b ,  float& c)
{		
  //cout << "pt1(" << pt1.first <<" , "<<pt1.second<< ") pt2("<< pt2.first<<" , "<<pt2.second<<")"<<endl;

  //equation wanted : aX + bY + c = 0
  a =  pt2.second -  pt1.second ;
  b = -( pt2.first   -  pt1.first );
  c = -(   a *  pt1.first  +   b * pt1.second    );

  return ;
}



void Pvcalc::print()
{
  int   Nhit	    = this->_width	;
  int   Nmiss	    = this->_height	; 
  float score     = this->_score  ;

  cout << "Nhit ="<< Nhit << " Nmiss=" << Nmiss << endl ;

  float Xoffset = score * ( Nhit  + 1.0 ) ;
  float Yoffset = score * ( Nmiss + 1.0 ) ;

  //cout << "Xoffest ="<< Xoffset << " Yoff=" << Yoffset << endl ;

  /*1st Equation , Minimum 'y'*/
  pair <float,float> pt1 ;
  pair <float,float> pt2 ;

  pt1.first  = Xoffset ;
  pt1.second = 0.0 ;

  pt2.first  = (float)Nhit ;
  pt2.second = (float)Nmiss - (float)Yoffset ;

  float a_min , b_min , c_min ;
  this->findEquation( pt1 , pt2 , a_min , b_min , c_min );

  //cout << "\n eq mini :" << a_min << " " <<  b_min << " " << c_min << endl ;


  /*2d Equation , Maximum 'y'	*/
  pt1.first  = 0 ;
  pt1.second = Yoffset ;

  pt2.first  = (float)Nhit - (float)Xoffset ;
  pt2.second = (float)Nmiss ;

  float a_max , b_max , c_max ;
  this->findEquation( pt1 , pt2 , a_max , b_max , c_max );

  // cout << "\n eq maxi :" << a_max << " " <<  b_max << " " << c_max << endl ;

  for( float  y = Nmiss ;  y >=   0 ;  y-- )
  {	
    cout << "\n[ " ;	
    for( float  x = 0.0 ; x <=   Nhit  ;  x++ )
    {	
      float  minY ;
      if( b_min != 0 ){ minY = ( -  c_min -  a_min *  x) /  b_min ;}else{ minY= -1.0;}

      float  maxY ;
      if( b_max != 0 ){ maxY = ( -  c_max -  a_max *  x) /  b_max ;}else{ maxY= Nmiss + 1.0;}


      cout << " " << x << "," << y << " " ;
      if(  minY <  y  &&   y <  maxY )
      {
	cout <<" X ";
      }
      else
      {
	cout <<" O " ;
      }
      cout <<"|" ;
    }
    cout <<" ]" ;
  }	

  cout <<"\n" ;   
}


